/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_JSFUCNTION_H
#define ECMASCRIPT_JSFUCNTION_H

#include "plugins/ecmascript/runtime/accessor_data.h"
#include "plugins/ecmascript/runtime/ecma_macros.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/js_function_extra_info.h"
#include "plugins/ecmascript/runtime/js_object-inl.h"
#include "plugins/ecmascript/runtime/lexical_env.h"

namespace ark::ecmascript {
using ark::coretypes::DynClass;
class JSThread;

class JSFunctionBase : public JSObject {
public:
    CAST_CHECK(JSFunctionBase, IsJSFunctionBase);

    inline void SetConstructor(bool flag)
    {
        JSHClass *hclass = GetJSHClass();
        hclass->SetConstructor(flag);
    }

    static bool SetFunctionName(JSThread *thread, const JSHandle<JSFunctionBase> &func,
                                const JSHandle<JSTaggedValue> &name, const JSHandle<JSTaggedValue> &prefix);
    static JSHandle<JSTaggedValue> GetFunctionName(JSThread *thread, const JSHandle<JSFunctionBase> &func);

    void SetCallTarget([[maybe_unused]] const JSThread *thread, JSMethod *p)
    {
        SetMethod(p);
    }

    ACCESSORS_BASE(JSObject)
    ACCESSORS_NATIVE_FIELD(0, Method, JSMethod)
    ACCESSORS_FINISH(1)
};

class JSFunction : public JSFunctionBase {
public:
    static constexpr int LENGTH_OF_INLINE_PROPERTIES = 3;
    static constexpr int LENGTH_INLINE_PROPERTY_INDEX = 0;
    static constexpr int NAME_INLINE_PROPERTY_INDEX = 1;
    static constexpr int PROTOTYPE_INLINE_PROPERTY_INDEX = 2;
    static constexpr int CLASS_PROTOTYPE_INLINE_PROPERTY_INDEX = 1;

    /* -------------- Common API Begin, Don't change those interface!!! ----------------- */
    CAST_CHECK(JSFunction, IsJSFunction);

    static void InitializeJSFunction(JSThread *thread, const JSHandle<GlobalEnv> &env, const JSHandle<JSFunction> &func,
                                     FunctionKind kind = FunctionKind::NORMAL_FUNCTION, bool strict = true);
    // ecma6 7.3
    static bool OrdinaryHasInstance(JSThread *thread, const JSHandle<JSTaggedValue> &constructor,
                                    const JSHandle<JSTaggedValue> &obj);

    static JSTaggedValue SpeciesConstructor(const JSHandle<JSFunction> &func,
                                            const JSHandle<JSFunction> &defaultConstructor);

    // ecma6 9.2
    // 7.3.12 Call(F, V, argumentsList)

    static JSTaggedValue Call(EcmaRuntimeCallInfo *info);
    static JSTaggedValue Invoke(EcmaRuntimeCallInfo *info, const JSHandle<JSTaggedValue> &key);

    static JSTaggedValue Construct(EcmaRuntimeCallInfo *info);
    static JSTaggedValue ConstructInternal(EcmaRuntimeCallInfo *info);

    static bool AddRestrictedFunctionProperties(const JSHandle<JSFunction> &func, const JSHandle<JSTaggedValue> &realm);
    static bool MakeConstructor(JSThread *thread, const JSHandle<JSFunction> &func,
                                const JSHandle<JSTaggedValue> &proto, bool writable = true);
    static bool SetFunctionLength(JSThread *thread, const JSHandle<JSFunction> &func, JSTaggedValue length,
                                  bool cfg = true);
    static JSHandle<JSObject> NewJSFunctionPrototype(JSThread *thread, ObjectFactory *factory,
                                                     const JSHandle<JSFunction> &func);
    static DynClass *GetOrCreateInitialDynClass(JSThread *thread, const JSHandle<JSFunction> &fun);
    static JSTaggedValue AccessCallerArgumentsThrowTypeError(EcmaRuntimeCallInfo *argv);
    static bool IsDynClass(JSTaggedValue object);
    static JSTaggedValue PrototypeGetter(JSThread *thread, const JSHandle<JSObject> &self);
    static bool PrototypeSetter(JSThread *thread, const JSHandle<JSObject> &self, const JSHandle<JSTaggedValue> &value,
                                bool mayThrow);
    static JSTaggedValue NameGetter(JSThread *thread, const JSHandle<JSObject> &self);
    static bool NameSetter(JSThread *thread, const JSHandle<JSObject> &self, const JSHandle<JSTaggedValue> &value,
                           bool mayThrow);
    static JSTaggedValue LengthGetter(JSThread *thread, const JSHandle<JSObject> &self);
    static bool LengthSetter(JSThread *thread, const JSHandle<JSObject> &self, const JSHandle<JSTaggedValue> &value,
                             bool mayThrow);
    static void SetFunctionNameNoPrefix(JSThread *thread, JSFunction *func, JSTaggedValue name);
    static JSHandle<DynClass> GetInstanceDynClass(JSThread *thread, JSHandle<JSFunction> constructor,
                                                  JSHandle<JSTaggedValue> newTarget);

    inline JSTaggedValue GetFunctionPrototype() const
    {
        ASSERT(HasFunctionPrototype());
        JSTaggedValue protoOrDyn = GetProtoOrDynClass();
        if (protoOrDyn.IsJSHClass()) {
            return JSHClass::Cast(protoOrDyn.GetTaggedObject())->GetPrototype();
        }

        return protoOrDyn;
    }

    inline void SetFunctionPrototype(const JSThread *thread, JSTaggedValue proto)
    {
        SetProtoOrDynClass(thread, proto);
        if (proto.IsJSHClass()) {
            proto = JSHClass::Cast(proto.GetTaggedObject())->GetPrototype();
        }
        if (proto.IsECMAObject()) {
            proto.GetTaggedObject()->GetClass()->SetIsPrototype(true);
        }
    }

    inline bool HasInitialDynClass() const
    {
        JSTaggedValue protoOrDyn = GetProtoOrDynClass();
        return protoOrDyn.IsJSHClass();
    }

    inline bool HasFunctionPrototype() const
    {
        JSTaggedValue protoOrDyn = GetProtoOrDynClass();
        return !protoOrDyn.IsHole();
    }

    inline DynClass *GetInitialDynClass() const
    {
        ASSERT(HasInitialDynClass());
        JSTaggedValue protoOrDyn = GetProtoOrDynClass();
        return reinterpret_cast<DynClass *>(protoOrDyn.GetTaggedObject());
    }

    inline void SetFunctionLength(const JSThread *thread, JSTaggedValue length)
    {
        ASSERT(!IsPropertiesDict());
        SetPropertyInlinedProps(thread, LENGTH_INLINE_PROPERTY_INDEX, length);
    }

    inline void SetupFunctionLength(const JSThread *thread)
    {
        SetFunctionLength(thread, GetMethod()->GetLength());
    }

    inline bool IsBase() const
    {
        FunctionKind kind = GetFunctionKind();
        return kind <= FunctionKind::CLASS_CONSTRUCTOR;
    }

    inline bool IsDerivedConstructor() const
    {
        FunctionKind kind = GetFunctionKind();
        return kind == FunctionKind::DERIVED_CONSTRUCTOR;
    }

    inline void SetFunctionKind(const JSThread *thread, FunctionKind kind)
    {
        JSTaggedType oldValue = GetFunctionInfoFlag().GetRawData();
        SetFunctionInfoFlag(thread, JSTaggedValue(FunctionKindBit::Update(oldValue, kind)));
    }

    inline void SetStrict(const JSThread *thread, bool flag)
    {
        JSTaggedType oldValue = GetFunctionInfoFlag().GetRawData();
        SetFunctionInfoFlag(thread, JSTaggedValue(StrictBit::Update(oldValue, flag)));
    }

    inline void SetResolved(const JSThread *thread)
    {
        TaggedType oldValue = GetFunctionInfoFlag().GetRawData();
        SetFunctionInfoFlag(thread, JSTaggedValue(ResolvedBit::Update(oldValue, true)));
    }

    inline bool IsResolved() const
    {
        return ResolvedBit::Decode(GetFunctionInfoFlag().GetInt());
    }

    inline void SetFunctionMode(const JSThread *thread, FunctionMode mode)
    {
        JSTaggedType oldValue = GetFunctionInfoFlag().GetRawData();
        SetFunctionInfoFlag(thread, JSTaggedValue(ThisModeBit::Update(oldValue, mode)));
    }

    inline FunctionKind GetFunctionKind() const
    {
        return FunctionKindBit::Decode(GetFunctionInfoFlag().GetInt());
    }

    inline bool IsStrict() const
    {
        return StrictBit::Decode(GetFunctionInfoFlag().GetInt());
    }

    inline FunctionMode GetFunctionMode() const
    {
        return ThisModeBit::Decode(GetFunctionInfoFlag().GetInt());
    }

    inline static bool IsArrowFunction(FunctionKind kind)
    {
        return (kind >= ARROW_FUNCTION) && (kind <= ASYNC_ARROW_FUNCTION);
    }

    inline static bool IsClassConstructor(FunctionKind kind)
    {
        return (kind == CLASS_CONSTRUCTOR) || (kind == DERIVED_CONSTRUCTOR);
    }

    inline static bool IsConstructorKind(FunctionKind kind)
    {
        return (kind >= FunctionKind::BASE_CONSTRUCTOR) && (kind <= FunctionKind::DERIVED_CONSTRUCTOR);
    }

    inline bool IsBuiltinConstructor()
    {
        FunctionKind kind = GetFunctionKind();
        return kind >= FunctionKind::BUILTIN_PROXY_CONSTRUCTOR && kind <= FunctionKind::BUILTIN_CONSTRUCTOR;
    }

    inline static bool HasPrototype(FunctionKind kind)
    {
        return (kind >= FunctionKind::BASE_CONSTRUCTOR) && (kind <= FunctionKind::ASYNC_GENERATOR_FUNCTION) &&
               (kind != FunctionKind::BUILTIN_PROXY_CONSTRUCTOR);
    }

    inline static bool HasAccessor(FunctionKind kind)
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
        return kind >= FunctionKind::NORMAL_FUNCTION && kind <= FunctionKind::ASYNC_FUNCTION;
#pragma GCC diagnostic pop
    }

    inline bool IsClassConstructor() const
    {
        return GetClass()->IsClassConstructor();
    }

    inline void SetClassConstructor(bool flag)
    {
        GetClass()->SetClassConstructor(flag);
    }

    /* -------------- Common API End, Don't change those interface!!! ----------------- */
    static void InitializeJSFunction(JSThread *thread, const JSHandle<JSFunction> &func,
                                     FunctionKind kind = FunctionKind::NORMAL_FUNCTION, bool strict = true);
    static JSHClass *GetOrCreateInitialJSHClass(JSThread *thread, const JSHandle<JSFunction> &fun);
    static JSHandle<JSHClass> GetInstanceJSHClass(JSThread *thread, JSHandle<JSFunction> constructor,
                                                  JSHandle<JSTaggedValue> newTarget);

    ACCESSORS_BASE(JSFunctionBase)
    ACCESSORS(0, ProtoOrDynClass)
    ACCESSORS(1, LexicalEnv)
    ACCESSORS(2, HomeObject)
    ACCESSORS(3, FunctionInfoFlag)
    ACCESSORS(4, FunctionExtraInfo)
    ACCESSORS(5, ConstantPool)
    ACCESSORS(6, ProfileTypeInfo)
    ACCESSORS_FINISH(7)

    static constexpr uint32_t FUNCTION_KIND_BIT_NUM = 5;
    using FunctionKindBit = BitField<FunctionKind, 0, FUNCTION_KIND_BIT_NUM>;
    using StrictBit = FunctionKindBit::NextFlag;

    using ResolvedBit = StrictBit::NextFlag;
    using ThisModeBit = ResolvedBit::NextField<FunctionMode, 2>;  // 2: means this flag occupies two digits.

    DECL_DUMP()
private:
    static JSHandle<JSHClass> GetOrCreateDerivedJSHClass(JSThread *thread, JSHandle<JSFunction> derived,
                                                         JSHandle<JSFunction> constructor,
                                                         JSHandle<JSHClass> ctorInitialJsHclass);
};

class JSGeneratorFunction : public JSFunction {
public:
    CAST_CHECK(JSGeneratorFunction, IsGeneratorFunction);

    static constexpr size_t SIZE = JSFunction::SIZE;

    DECL_DUMP()
};

class JSAsyncFunction : public JSFunction {
public:
    CAST_CHECK(JSAsyncFunction, IsJSAsyncFunction);

    static constexpr size_t SIZE = JSFunction::SIZE;

    DECL_DUMP()
};

class JSAsyncGeneratorFunction : public JSFunction {
public:
    CAST_CHECK(JSAsyncGeneratorFunction, IsAsyncGeneratorFunction);

    static constexpr size_t SIZE = JSFunction::SIZE;

    DECL_DUMP()
};

class JSConstructorFunction : public JSFunction {
public:
    enum class PrivateFieldKind { FIELD, METHOD, GET, SET, STATIC_FIELD, STATIC_METHOD, STATIC_GET, STATIC_SET };

    CAST_CHECK(JSConstructorFunction, IsClassConstructor);

    ACCESSORS_BASE(JSFunction)
    ACCESSORS(0, ComputedFields)
    ACCESSORS(1, PrivateFields)
    ACCESSORS_FINISH(2)

    DECL_DUMP()
};

class JSBoundFunction : public JSFunctionBase {
public:
    CAST_CHECK(JSBoundFunction, IsBoundFunction);

    // 9.4.1.2[[Construct]](argumentsList, new_target)
    static JSTaggedValue ConstructInternal(EcmaRuntimeCallInfo *info);

    ACCESSORS_BASE(JSFunctionBase)
    ACCESSORS(0, BoundTarget)
    ACCESSORS(1, BoundThis)
    ACCESSORS(2, BoundArguments)
    ACCESSORS_FINISH(3)

    DECL_DUMP()
};

class JSProxyRevocFunction : public JSFunction {
public:
    CAST_CHECK(JSProxyRevocFunction, IsProxyRevocFunction);

    static void ProxyRevocFunctions(const JSThread *thread, const JSHandle<JSProxyRevocFunction> &revoker);

    ACCESSORS_BASE(JSFunction)
    ACCESSORS(0, RevocableProxy)
    ACCESSORS_FINISH(1)

    DECL_DUMP()
};

// ResolveFunction/RejectFunction
class JSPromiseReactionsFunction : public JSFunction {
public:
    CAST_CHECK(JSPromiseReactionsFunction, IsJSPromiseReactionFunction);

    ACCESSORS_BASE(JSFunction)
    ACCESSORS(0, Promise)
    ACCESSORS(1, AlreadyResolved)
    ACCESSORS_FINISH(2)

    DECL_DUMP()
};

// ExecutorFunction
class JSPromiseExecutorFunction : public JSFunction {
public:
    CAST_CHECK(JSPromiseExecutorFunction, IsJSPromiseExecutorFunction);

    ACCESSORS_BASE(JSFunction)
    ACCESSORS(0, Capability)
    ACCESSORS_FINISH(1)

    DECL_DUMP()
};

class JSPromiseAllResolveElementFunction : public JSFunction {
public:
    CAST_CHECK(JSPromiseAllResolveElementFunction, IsJSPromiseAllResolveElementFunction);

    ACCESSORS_BASE(JSFunction)
    ACCESSORS(0, Index)
    ACCESSORS(1, Values)
    ACCESSORS(2, Capabilities)
    ACCESSORS(3, RemainingElements)
    ACCESSORS(4, AlreadyCalled)
    ACCESSORS_FINISH(5)

    DECL_DUMP()
};

class JSIntlBoundFunction : public JSFunction {
public:
    CAST_CHECK(JSIntlBoundFunction, IsJSIntlBoundFunction);

    static JSTaggedValue IntlNameGetter(JSThread *thread, const JSHandle<JSObject> &self);

    ACCESSORS_BASE(JSFunction)
    ACCESSORS(0, NumberFormat)
    ACCESSORS(1, DateTimeFormat)
    ACCESSORS(2, Collator)
    ACCESSORS_FINISH(3)

    DECL_DUMP()
};
}  // namespace ark::ecmascript

#endif  // ECMASCRIPT_JSFUCNTION_H
